package com.dsz.apigateway;

import com.dsz.apiclientsdk.utils.SignUtil;
import com.dsz.apicommon.model.domain.InterfaceInfo;
import com.dsz.apicommon.model.domain.User;
import com.dsz.apicommon.service.InnerInterfaceInfoService;
import com.dsz.apicommon.service.InnerUserInterfaceInfoService;
import com.dsz.apicommon.service.InnerUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

/**
 * 全局过滤器
 *
 * @author dushangzhi
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    @DubboReference
    private InnerUserService innerUserService;
    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;
    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;

    //白名单
    private static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发起请求
        //2.记录请求日志
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求id: " + request.getId());
        String url = request.getURI().toString();
        log.info("请求路径: " + url);
        String method = request.getMethod().toString();
        log.info("请求方法: " + method);
        log.info("请求参数: " + request.getQueryParams());
        String sourceAddress = request.getLocalAddress().getHostString();
        log.info("请求源地址: " + sourceAddress);
        //3.黑白名单
        ServerHttpResponse response = exchange.getResponse();
        //如果ip不在白名单中
        if (!IP_WHITE_LIST.contains(sourceAddress)) {
            return handleNoAuth(response);
        }
        //4.验证接口是否存在
        InterfaceInfo interfaceInfo = null;
        try {
            interfaceInfo = innerInterfaceInfoService.getUserInterfaceInfo(url, method);
        } catch (Exception e) {
            log.info("获取接口信息时出现异常" + e);
        }
        if (interfaceInfo == null) {
            return handleNoAuth(response);
        }

        //todo 还需要判断用户是否有调用次数

        //5.用户鉴权
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String sign = headers.getFirst("sign");
        String body = headers.getFirst("body");
        String nonce = headers.getFirst("nonce");
        //实际应该去数据库中查询secretKey
        User userInfo = null;
        try {
            userInfo = innerUserService.getUserInfo(accessKey);
        } catch (Exception e) {
            log.info("获取用户信息出现异常" + e);
        }
        if (userInfo == null) {
            return handleNoAuth(response);
        }
        String serverSign = SignUtil.getSign(body, userInfo.getSecretKey());
        //随机数校验(此处为了方便)
        if (Long.parseLong(nonce) > 10000) {
            return handleNoAuth(response);
        }
        //时间戳校验
        long timestamp = Long.parseLong(headers.getFirst("timestamp"));
        long currentTime = System.currentTimeMillis() / 1000;
        final long FIVE_MINUTES = 5 * 60L;
        if (currentTime - timestamp >= FIVE_MINUTES) {
            return handleNoAuth(response);
        }
        //签名认证，实际应该是去数据库中查询
        if (sign == null || !serverSign.equals(sign)) {
            return handleNoAuth(response);
        }
        //6.请求转发
        Mono<Void> filter = chain.filter(exchange);
        log.info("响应码: " + response.getStatusCode().toString());
        //7.记录响应日志
        return handleResponse(exchange, chain, userInfo.getId(), interfaceInfo.getId());
    }

    /**
     * 自定义响应处理
     */
    private Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, Long userId, Long interfaceInfoId) {
        try {
            //获取原始响应对象
            ServerHttpResponse originalResponse = exchange.getResponse();
            //获取数据缓存工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            //获取响应的状态码（此处有些疑惑，按理说现在还没有调用接口怎么就能拿到响应码呢？）
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                //对于非200 OK的请求，降级处理返回数据
                return chain.filter(exchange);
            }
            //创建一个装饰后的响应对象（能力增强）
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                //重写writewith方法，用于处理响应体的数据
                //这段方法就是只要当我们的模拟接口调用完成之后,等它返回结果，就会调用writewith方法,我们就能根据响应结果做一些自己的处理
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    //判断响应体是否是Flux类型
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        //返回一个处理后的响应体(这里就理解为它在拼接字符串,它把缓冲区的数据取出来，一点一点拼接好)
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            //8.请求成功，次数+1
                            try {
                                innerUserInterfaceInfoService.invokeCount(userId, interfaceInfoId);
                            } catch (Exception e) {
                                log.info("接口调用次数+1时出现异常" + e);
                            }
                            // 合并多个流集合，解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer buff = dataBufferFactory.join(dataBuffers);
                            //读取响应体的内容并转换为字节数组
                            byte[] content = new byte[buff.readableByteCount()];
                            buff.read(content);
                            DataBufferUtils.release(buff);//释放掉内存

                            // 构建日志
                            String data = new String(content, StandardCharsets.UTF_8);
                            log.info("响应内容: " + data);
                            return bufferFactory.wrap(content);
                        }));
                    } else {
                        //9.请求失败，返回规范的错误码
                        log.error("<-- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            //对于200 OK的请求，将装饰后的响应对象传递给下一个过滤器继续处理
            return chain.filter(exchange.mutate().response(decoratedResponse).build());
        } catch (Exception e) {
            //处理异常情况，记录错误日志
            log.error("网关处理响应异常" + e);
            //这应该也属于降级吧
            return chain.filter(exchange);
        }
    }

    /**
     * 无权限
     */
    private Mono<Void> handleNoAuth(ServerHttpResponse response) {
        //拦截请求
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    /**
     * 请求失败
     */
    private Mono<Void> handleInvokeError(ServerHttpResponse response) {
        //拦截请求
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        return response.setComplete();
    }


    @Override
    public int getOrder() {
        return -1;
    }
};